package JavaEE.NetWork3;
// 应用层:
// 自定义应用层协议(约定前后端交互接口)
// 1. 确定传输的信息 -> 产品需求
// 2. 确定传输的格式(*重要*) -> 行文本/xml/json/ProtoBuffer
//  以"QQ发送hello为例":(发送方id,接收方id,时间,内容)
//  1) 行文本:
//  "1234,5678,2025-5-3 20:09:00" (可读性差)
//  2) xml:(引入了很多标签,占用较多网络带宽,成本高,不常用)
//  <request>
//     <from>1234</from>
//     <to>...
//  <request>
//  3) json:(*重点*) -> (大大提高了可读性,并且减少网络带宽的使用)
//  {
//      "from":1234,
//      "to":5678,
//      "time":"2025-5-3 20:09:00",
//      "message":"hello"
//  }
//  4) google protoBuffer(二进制压缩方案,可读性非常差!!!网络带宽消耗非常少!!!)
//

// 协议格式:
/** UDP协议: **/
// 分为"UDP报头"和"UDP"载荷
// 1.UDP报头:
//  1&2) 源/目的端口:
//  大小2个字节,无符号,0~65535(程序员使用通常避开0~1024)
//  3) UDP长度:
//  大小2个字节,0~65535->64kb,说明一个UDP数据包最大长度为64kb
//  而当前64kb有时不够用,解决方案:1.应用层对数据拆分(复杂,难实现) 2.(换成TCP协议,没有明确上限)
//  4) 校验和:
//  大小2个字节,检验UDP数据包在传输中是否出错.
//  因为由"物理层"传输,本质是"光/电信号",由于"高/低电平"或"环境影响",则可能出现错误
//  校验和的原理:
//  构造UDP数据包完成后,把数据包的每个字节的数据进行累加成一个16位整数上
//  接收方收到UDP数据包,再次累加,判断两次是否相等,如果不相等则直接把数据丢弃掉.
//

/** TCP协议: **/
// 与UDP除了"有连接","可靠传输","面向字节流"以外,还有更多的差别(TCP很复杂)

// 1.TCP报头:
// 报头的长度不是固定的,其中包含"可选的",代表"可以有多个",所以"报头可变长"
// UDP想升级,非常费劲(无法一次性都升级,在线的'旧'和'新'就冲突),导致UDP的报文长度始终受到64kb的限制
// _________________________
// | U | A | P | R | S | F |
// | R | C | S | S | Y | I |
// | G | K | H | T | N | N |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// URG(紧急位)
// 作用:指示报文段中包含紧急数据,需要优先处理
// 场景:终端输入Ctrl+C中断命令时,发送方会设置URG标志
// ACK(确认位)
// 作用:确认已收到数据,值为1时表示报头中的"确认号"有效
// 特性:TCP连接建立后,所有报文必须设置ACK=1
// PSH(推送位)
// 作用:要求接收方立即将数据交给应用层,而非缓冲
// 场景:实时应用,避免等待缓冲区填满(flush)
// RST(复位位)
// 作用:强制终止连接,通常表示异常情况(端口未监听,连接错误)
// 行为:收到RST的端会立即释放连接,不发送确认
// 场景:客户端尝试连接未监听的端口时,服务端返回RST
// SYN(同步位)
// 作用:发起连接请求,同步序列号
// 场景:第一次握手:客户端发送SYN=1,ACK=0,携带初始序列号(ISN)
//     第二次握手:服务端回复SYN=1,ACK=1,携带自己的ISN
// FIN(终止位)
// 作用:优雅关闭连接,表示发送方数据已发送完毕
//
// 2.TCP保留位:
// 但TCP有"保留位",现在可能不用,但先占个位置,说不定以后就会使用.
// 保留位的作用是避免协议升级时的兼容性问题

// 3.TCP可靠传输:
// 可靠传输并不是指"100%传输成功"(做不到),而是指"传输成功或失败,发送方能知道".

// 4.TCP序号和确认序号(TCP核心机制一)
// 在网络通信中,信息的传送会途经很多"路由器/交换机",这就相当于"路上的红路灯"
// 而这样就可能出现"后发先至"的情况,而为了防止这种情况,就需要能知道"发送顺序"
// "序号和确认序号"就是"对传输的数据进行标识"~
// 比如:A发送了一条信息,标记为"1"(序号),B回复这条信息时,标记为"针对1"(确认序号)
// TCP数据包,包含多个字节,如何体现所有的编号呢?:编号是连续递增的,知道TCP载荷的第一个字节编号即可.
// 确认序号(ACK)的含义:
// (所有<确认序号的数据,认为接收方已经收到了.   发送方接下来应该从确认序号位置,继续发送数据)

// 5.TCP超时重传(TCP核心机制二)
// 重传:发现数据丢包,就再发送一次
// 超时:只要超过超时时间,还没有收到ACK,就可以视为是丢包了.

// 6.TCP接收缓冲区:
// 发送或应答,都可能丢,而"没发过去",重传是理所应当的.但"没收到回复"还重传,
// 就导致"接收方接收两次",就可能"收到两次响应",不合理"TCP缓冲区自动去重"
// 并且TCP接收缓冲区内部会对收到的数据进行重排序,比如可能"先收到1001~2000"
// 后收到"1~1000",那么就会自动按照"发送顺序"进行升序的重排序,

/** 最最最重点的部分!!! **/
// 7.TCP连接管理(TCP核心机制三)
// 连接:通信双方各自保存对方的关键信息(IP,端口...)
// 连接的建立:三次握手
// 连接的断开:四次挥手
// ---TCP建立连接(三次握手) SYN -> SYN+ACK -> ACK ---
//  1).客户端给服务器发送一个syn(同步报文段)
//  客户端告诉服务器"我要和你建立连接,请你保存我的信息"
//  2).服务器给客户端返回一个ack(与3一起视为一次握手)
//  服务器告诉客户端"收到,我会保存"
//  3).服务器也给客户端发起一个syn(与2一起视为一次握手)
//  服务器也告诉客户端"我也要和你建立连接,请你保存我的信息"
//  4).客户端收到之后,返回一个ack
//  客户端告诉服务器"收到,我会保存"
// 三次握手解决什么问题?
//  1).三次握手判断通信链路是否通畅
//  "三次握手可以视为是(保证可靠传输的辅助手段/前提条件),因为如果
//  (三次握手都办不到)则代表通信链路不通畅,更别谈做到(可靠传输)了"
//  2).三次握手判断双方发送和接收是否通畅
//  "客户端->服务器:判断出,客户端发送正常,服务器接收正常(服务器知道,客户端不知道)"
//  "服务器->客户端:判断出,服务器发送正常,客户端接收正常(客户端知道,服务器不知道)"
//  "客户端->服务器:客户端告诉服务器,咱俩的接收和发送正常"
//  3).三次握手让通信双方协商关键信息(TCP数据的起始序号)
//  防止极端情况:可能在"第一次连接"中,某段数据"迷路了",导致到的很晚
//  而当它到的时候,"第一次连接"已经断开,并建立了"第二次连接",那这个数据肯定要丢弃
//  而判断"是否为上次连接中的数据",就通过"比较序号",如果序号和起始序号相差过大,则扔掉,否则反之.
//  LISTEN状态:服务器存在
//  ESTABLISHED状态:客户端与服务器连接建立好了
// ---TCP断开连接(四次挥手) FIN -> ACK -> FIN -> ACK ---
// 断开连接的四次挥手,可能由客户端发起,也可能为服务器发起
//  1).客户端给服务器发送一个fin
//  客户端告诉服务器"我要和你断开连接,请你把我删除"
//  2).服务器给客户端返回一个ack
//  服务器告诉客户端"收到,我会删除"
//  3).服务器也给客户端发起一个fin
//  服务器也告诉客户端"我也要和你断开连接,请你把我删除"
//  4).客户端收到之后,返回一个ack
//  客户端告诉服务器"收到,我会删除"
// *为什么TCP断开连接时,23没有合并?
// 1.TCP的四次挥手,2,3步不合并的情况(常态)
// 和三次握手本质区别在于"四次挥手不是完全由操作系统内核完成的,而是和应用层代码有关"
// 客户端:"进程退出"或"调用close()"都可能触发FIN,而ACK才是完全由内核控制的
// 服务器:大概率由"socket.close()"触发FIN
// 而客户端给出"ACK"后,代码还要运行到"close",才会FIN,其中不知道消耗多长时间,所以不能视为同一步~
// 2.TCP的四次挥手,2,3步合并的情况(极端)
// 特殊情况:在某些情况符合时,可以合并
// *"TCP存在(延时应答机制)"
// 延时应答:应答ack的时机会往后拖一段时间(短).
// 如果说"服务端收到INF"和"服务端触发close()"的相隔时间不长,再加上"延时应答"
// 就可以把"返回ack"和"发送INF"合并成一步
// 好处:能有效减少报文个数,提高效率.
//
/** 建立链接的时候,必须握手三次吗？两次不可以吗？四次呢？ **/
// 四次:可以,但是没必要,2,3步时机相同,合并成一次可减少报文个数,提高效率
// 两次:不可以,这样会导致服务端没有收到syn报文,不知道"发送和接收是否相互正常"
//
/** 断开链接的时候,必须挥手四次吗？三次不可以吗？**/
// 几乎必须是挥手四次,因为其中"服务端发送FIN"不是由操作系统内核进行的
// 这也就代表了,"服务端传回syn报文"和"服务端发送FIN"的时间间隔通常不能视为同一时机
// 所以"几乎"必须是挥手四次
// 但也存在极端情况,比如TCP触发了"延时应答"机制,导致"服务端传回syn报文"发生的时间较晚
// 并且由于代码立即执行close,使"服务端传回syn报文"和"服务端发送FIN"的时间间隔较短
// 就可以视作2,3为同一步,如果符合条件,操作系统就会这样做,因为这样能够减少报文个数,提高效率
//
// 8.TCP滑动窗口(核心机制四)
// 前面的核心机制,都是为了"实现TCP的可靠性",而为了实现"可靠性",付出的代价就是"传输效率低"
// 滑动窗口的作用:
// 提高效率.
// 滑动窗口的原理:
// TCP的机制导致,每次发送一个数据,都要等待ack,这样就导致效率下降
// 那么如果我们把"每次发送一个数据" -> "每次发送一批数据",然后再等待ack
// 就能够做到"花一份等待ack的时间,来等待多个ack",这样效率就能够提升了.
// 1   1001  2001  3001  4001  5001  6001  7001  8001  9001
// |     |     |     |     |     |     |     |     |     |
// 比如我们一次性发送了1001~5000,那么就是同时等待四组ack,窗口大小为4
// 当收到2001的ack时,就视为1001~2000已经收到,则可以发送5001~6000(窗口滑动)
// 注:TCP滑动窗口只是补救,最理想可以使效率接近UDP,但不可能超过.
// 情况1:数据包抵达,但ack丢了(最后一个ack丢包,另当别论)
// 如果只是ack丢包,那么在滑动窗口的机制下,不需要做任何处理!
// 如果传回一个ack的确认序号,代表该序号之前的所有数据都已经收到,即(后一个ack会涵盖前一个ack)
// 比如1001的ack丢包了,但2001的ack成功传回,则代表1~2000都已经收到
// 情况2:数据包丢包(快速重传的应对方法)
// 比如主机A向主机B发送1~1000,并且得到了ack,则代表主机A可以继续发送1001~2000
// 而此时,1001~2000数据包发送时发生了丢包,则代表主机B没收到1001~2000
// 但主机A不知道,则继续发送2001~3000的数据包,但仍然只能得到1001的ack数据包
// 以此循环多次(收到多次相同的),主机A才会发现问题,于是重传1001~2000
// 此时B观察,如果(B的接受缓冲区中存在2001~6000'假设')则返回6001的ack数据包,
// 如果(2001后没有紧跟着数据包"可能传2001~3000"时也丢包了),则返回2001的ack数据包.
//
// 9.TCP流量控制(核心机制五)
// 目的:防止发送方发的太快,接收方处理不过来(缓冲区溢出) -> (控制滑动窗口的发送速率)
// 机制:接收方通过"TCP头部窗口字段",告知剩余缓冲区大小(rwnd)
//     发送方根据rwnd调整发送速率(窗口大小)
// 零窗口检测:如果rwnd=0,发送方会定时发送探测报文,避免死锁
// 头部窗口字段:(大小为16位 -> 64kb)
// 但并不意味着"TCP滑动窗口大小"最大只能是64kb.因为TCP设计,吸取了UDP的经验
// 选项中有"窗口扩展因子":发送方收到ack之后,设置的滑动窗口大小 = 16位窗口大小 << 窗口扩展因子
// 左移一位 指数增长.由此看来 窗口大小的取值范围,是非常大的
// 特殊情况(剩余缓冲区大小为0) 设发送方为"主机A" 接收方为"主机B":
// 当剩余缓冲区大小为0时,主机A便会停止发送(如果不停止发送,会导致数据包'丢包'被B丢弃,而再重传,得不偿失~)
// 而过了一会儿(周期性的),主机B消费了一定的数据,此时主机A发送一个'窗口检测包',
// 然后主机B再返回ack,告知下次该传的数据,以及此时"剩余缓冲区大小"
//
// 10.TCP拥塞控制(核心机制六)
// 目的:依据接收方的处理能力,进行限制(滑动窗口的发送速率)
//     (发送方的速率,不光要考虑接收方的速率,还要考虑传输路径整个过程中,所有中间节点的情况.)
// 机制:由于整个过程,"中间节点过多"且"路径存在随机性",从而使"直接计算"较为复杂,难以实现.
//     所以就可以采取(试试看),先按比较小的速度发送数据,如果(丢包),代表中间链路节点顶不住,减小窗口大小.
//     如果(不丢包),按照"剩余缓冲区大小",可以在尝试增大窗口大小.
//
// 11.找到"滑动窗口"合适大小的方法:
//  1) 初始情况下,窗口非常非常小 因为刚开始,网络的畅通情况未知.
//  2) 慢启动之后,如果不丢包,就会按照指数方式增长(短时间内把窗口大小顶上去)
//  3) 指数增长到一定程度(达到阈值),指数增长变成线性增长
//  4) 线性增长到某一时刻,触发丢包(网络的承载能力达到上限)
//  5) 重新计算阈值(丢包的窗口大小 / 2),从阈值开始作为新的拥塞窗口,继续线性增长
//
// 12.TCP延时应答(核心机制七)
// 作用:提高效率(减少ACK数量,提高带宽利用率,适应滑动窗口)
// 举例:(快递员送货)
//  1)传统应答:快递员每次送一个包裹(数据包)给顾客,顾客必须马上打电话(ACK)说"收到包裹了!"
//    结果:快递员频繁往返,大部分时间浪费在"等顾客打电话确认",效率极低
//  2)延时应答:快递员送完一个包裹后,顾客不立刻打电话,而是稍等片刻.
//    如果在这段等待时间内,快递员又送来了第二个包裹,顾客可以一次性打电话说"两个包裹都收到了!"
//    结果:减少了不必要的电话次数,快递员可以更专注于送货,效率提升
// 等待期间可能触发的优化: --TCP捎带应答(核心机制八)--
//  1)如果在等待期间,接收方有数据要发送(比如响应报文),就可以把这个ACK
//    捎带在这个数据包中(TCP头部包含ACK标志),省去单独发送ACK的开销.
//  2)如果在等待期间,接收方又收到了新的数据包,就可以合并成一个ACK,确认多个数据包(比如确认序列号到最新位置)
// 提高效率的关键点:
//  1)减少ACK数量:合并或捎带ACK,减少网络中的小包(类似减少"快递员接电话"次数)
//  2)提高带宽利用率:让发送方在等待ACK的时间内继续发送更多数据(快递员趁没收到电话,多发几件货)
//  3)适应滑动窗口:延迟ACK能让接收方在确认时携带更新的窗口大小信息,帮助发送方动态调整发送速率
//
// 13.TCP面向字节流(核心机制九)
// 由于TCP面向字节流,则使read和writer都很灵活
// 从而导致,接收方的应用程序,read的时候就有很多种 read 的可能性
// 比如此时我们输入"aaa","bbb","ccc"
// read的可能性:a a a b b b c c c / aa ab bb cc c / aaa bbb ccc / aaab bbcc c
// 很明显,aaa bbb ccc 才是对的,但是由于read的可能性,不一定保证正确,就引发了"粘包问题"
// "粘包问题":
// 粘包,粘的就是"应用层的数据包",TCP字节流的特性,收到多个TCP数据包的时候,把所有的载荷都混到一起
// 然后放到接收缓冲区里,而由于"包的边界比较模糊",就好像"粘上了"一样.
// 解决粘包问题:
//  1)通过特殊分隔符,作为包边界的区分:
//    如传输"aaa;","bbb;","ccc;",并且"约定每个应用层数据包"都以";"结尾.就能解决粘包问题
//  2)在应用层数据包开头的地方,通过固定长度,约定整个应用层数据包的长度:
//    如传输"3aaa","4bbbb","5ccccc",就能够有效的解决粘包问题.
//
// 14.TCP异常情况(核心机制十) -> "*心跳包"
// 进程崩溃:
// (调用close,或者干掉进程)只要是进程退出,就会释放PCB(触发FIN),和正常的四次挥手一样~
// 主机关机(正常流程):
// 正常流程下的主机关机,会先杀死所有的进程(向所有进程发送终止信号"非FIN")(进程触发FIN,进而进入四次挥手)
//  1)关机速度比较快:很有可能四次挥手,都挥完
//  2)关机速度比较慢:有可能刚触发FIN,机器就关了.
//                对端可以正常返回ack,也会继续发送FIN,但收不到ack了(主机关闭)
//                所以尝试重传几次FIN后,还是没有ack,就会直接放弃链接.
// 关机快慢的原因:进程清理和TCP挥手是异步过程,操作系统不会"等待所有FIN完成",而是有超时机制.
// 比喻:"火车到终点站(操作系统收到关机指令),广播通知下车(向进程发送终止信号)
//      乘客收拾行李准备下车(进程执行清理操作),有的乘客东西丢了找东西(异常或阻塞),
//      到了规定时间(操作系统的"关机超时时间"),也只能不找了,先下车(强制断开连接)"
// 主机掉电(直接拔电源):
//  1)发送端掉电,接收端正常:
//    发送端立刻失去所有电源,操作系统"没有机会"执行任何清理操作(包括发送FIN),物理链路层中断
//    对于TCP来说:发送端"消失",但连接理论上仍然存在,因为"接收端不知情"
//    那么此时,接收端就会继续阻塞等待,等待发送端发来新的数据.(但这是等不到的)
//    解决方案:--"心跳包"--
//    接收方会周期性的和发送方交换"心跳包":A给B发一个无业务数据的报文,B给A返回一个ack
//    如果对方有应答,就认为对方正常工作,如果心跳包没有应答,就可以认为对方挂了.
//  2)接收端掉电,发送端正常:
//    接收端立刻失去所有电源,发送端继续发送数据,但得不到ack->超时重传->仍然没有ack->继续超时重传
//    直到某一时刻,掉电方仍然没有ack,发送方就会发送一个"复位报文",代表"重置连接,放弃当前连接"
// 网线断开:
// 和"主机掉电"是一样的,网线断开代表(发送端掉电)和(接收端掉电)这两个情况一起发生,单独一边的情况和上述一样.
//
public class Demo1 {
    public static void main(String[] args) {

    }
}
